Skip to content

issue: Make the Listener and Stream filter-only and auto-materialize on save - #302

Merged
TheWitness merged 8 commits into
developfrom
fix/remove-listener-stream
Nov 3, 2026
Merged

TheWitness merged 8 commits into
developfrom
fix/remove-listener-stream

Conversation

@TheWitness

Copy link
Copy Markdown
Member

Summary

On the FlowView report tab, the Listener and Stream dropdowns are part of what a Filter materializes, so changing them there ran the report against data that was never materialized (always a cache miss / "fresh" scan). This removes both dropdowns so those settings live only in the Filter editor, reorders the New button to follow Edit, and makes a save that changes what is cached start a materialization automatically.

Changes

Listener / Stream are filter-only

  • Removed the Listener (device_id) and Stream (ex_addr) dropdowns from the report filter bar.
  • run_flow_query() now reads the Listener and Stream straight from the saved Filter row, never from the page request.
  • The report page no longer sends, validates, seeds or saves device_id/ex_addr: dropped from flowview_request_vars() validation, flowview_seed_filter_request_vars(), load_session_for_filter(), the top-tab Save (save_filter_form), Save As, and the chart/table/apply JavaScript. device_id is also dropped from the session cache key.
  • They remain fully editable in the Filter editor, where they belong.

Button order

  • New now comes after Edit.

Auto-materialize on a cache-affecting save

  • Saving a Filter (from the editor or the FlowView tab) with a change that alters what it materializes now calls flowview_launch_materialize() for that Filter and raises a message: "…a materialization of it has been started in the background. Its progress is shown on the Filters page…".
  • The old post-save dialog that asked the user to run Materialize by hand (and its flowview_show_saved_dialog / flowview_rematerialize_recommendation helpers) is removed. The pre-save Continue/Cancel warning is reworded to say saving starts the materialization.

Testing

  • Updated/added unit tests: markup now asserts the Listener/Stream dropdowns are absent and New follows Edit; the editor and top-tab saves assert the materialization is launched and the message raised; run_flow_query/flowview_plan_filter assert the exporter address comes from the saved Filter, not the page. Added shared exec_background/cacti_escapeshellarg test stubs.
  • Full unit + security suite: 375 passed, 3 skipped, 0 failed.
  • locales/po/cacti.pot regenerated; the i18n gate passes locally.
  • Verified in a browser on a scratch Cacti 1.2.32 install: the dropdowns are gone, the buttons read Go / Clear / Edit / New / Save / Save As / Rename / Delete, a filter loads its saved Sort Field, and a material editor save shows the "materialization has been started in the background" message.

…on save

The Listener and Stream are part of what a Filter materializes, so changing
them on the report page ran the report against data that was never
materialized.  Remove both dropdowns from the FlowView tab; they are set only
in the Filter editor now, and run_flow_query() reads them straight from the
saved Filter rather than from the page.  The report page no longer sends,
validates, seeds or saves device_id/ex_addr, and they are dropped from the
session cache key.  The New button now follows Edit.

When a Filter is saved with a change that affects its cached results, from the
editor or the FlowView tab, a materialization is now started in the background
for it and a message says so, instead of a dialog asking the user to start one
by hand.  The old post-save dialog and its recommendation helper are removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 23, 2026 20:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Saves can drop required rebuilds during active materialization, and graph drilldowns lose their listener/exporter scope.

2 open issues
What changed in this PR

This PR makes saved Filters the sole source of Listener and Stream settings for FlowView reports and automatically starts materialization after cache-affecting saves.

Changes:

  • Removes Listener and Stream controls and request overrides from the report page, and moves New after Edit.
  • Replaces manual rematerialization guidance with automatic background launches and updated notifications.
  • Updates regression tests, shared test stubs, translation strings, and the changelog.
File Description
tests/​Unit/​FlowviewFilterControlsTest.php Checks removed dropdowns and button order.
tests/​Unit/​FilterEditAndSaveTest.php Tests automatic materialization and filter-only settings.
tests/​Unit/​CoreFunctionsTest.php Updates dialog and saved-exporter behavior tests.
tests/​bootstrap-unit.php Adds background-execution and shell-escaping stubs.
locales/​po/​cacti.pot Refreshes translated message templates.
includes/​functions.php Updates report controls, query scope, and save-triggered materialization.
flowview.php Removes Listener and Stream request overrides and session seeding.
CHANGELOG.md Documents filter-only controls and automatic materialization.

Review effort: Balanced

Comment thread includes/functions.php Resolved
Comment thread includes/functions.php Resolved
bmfmancini
bmfmancini previously approved these changes Oct 24, 2026
somethingwithproof and others added 3 commits October 24, 2026 00:55
Resolve two reviewer comments on the filter-only Listener/Stream PR.

Queue a fresh materialization when a Filter is saved while one is
already running for it. flowview_launch_materialize() leaves a Filter
that is already being materialized to the active run, which works from
the settings as they were, so the new settings went uncached. The save
now detects that nothing was launched, records the Filter in a queue,
and tells the user the rebuild is queued rather than started. The poller
drains the queue with flowview_launch_queued_rematerializations() once no
materialization is running, dropping any Filter that was deleted.

Point the NetFlow graph drilldown at a saved Filter scoped to the
graph's exporter. The report page now reads the Listener and Stream from
the Filter, so flowview_graph_button()'s ex_addr and device_id overrides
were ignored and the icon showed every exporter under the default
Filter. It now links to the Filter matched to the graph's exporter and
is omitted when none matches.

Covered by new Pest tests for the queue helpers, the queued save paths
and the existing suite (387 passing).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add Pest tests for the two early-return branches the changed-line
coverage gate flagged: flowview_queue_rematerialize() ignoring a
non-positive id, and flowview_launch_queued_rematerializations()
returning early when nothing is queued.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@somethingwithproof somethingwithproof left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker: flowview_graph_button() still selects LIMIT 1 by device_id only. A listener can have saved Filters for multiple ex_addr values, so the new query-only URL can point at an arbitrary Filter for the right listener but the wrong stream; the removed request override can no longer correct it. Match the saved Filter on both listener and exporter, including the defined All-filter sentinels, make the choice deterministic, and cover two Filters on one listener.

Blocker: flowview_rematerialize_queue is a read-modify-write comma-separated setting with no synchronization. Two concurrent saves can overwrite one another, and the poller can clear a Filter queued after its stale read; that Filter will never be rebuilt under its saved settings. Persist queue entries with an atomic keyed or transactional protocol and add overlapping save-and-drain coverage.

Scope the NetFlow graph drilldown to both the graph host's Listener and
Stream. flowview_graph_button() matched a saved Filter on device_id alone
with LIMIT 1, so a Listener with Filters for several streams could drill into
the wrong one and the removed request override could no longer correct it. The
new flowview_graph_button_filter() helper matches on both the Listener and the
Stream (including the All-listener and All-stream sentinels), prefers the most
specific Filter deterministically, and returns nothing when none matches so the
link is omitted rather than pointing at the wrong exporter.

Make the rematerialize queue atomic. The queue was a comma-separated setting
read, modified and written in place, so two concurrent saves could overwrite
one another and the poller could clear a Filter queued after its stale read.
Each Filter is now its own settings row keyed by id with a token value; the
poller compare-and-deletes only the token it read, so concurrent saves never
lose one another and a Filter re-queued while the poller drains is kept.

Tests: rewrite the queue tests for the keyed protocol, add overlapping
save-and-drain coverage, and cover the graph-button Filter scoping. Full suite
393 passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@TheWitness

Copy link
Copy Markdown
Member Author

Fixed in 5f198d0.

Blocker 1 — graph drilldown matched the Listener only. flowview_graph_button() no longer does SELECT * ... WHERE device_id = ? LIMIT 1. The selection moved into a new, unit-tested helper flowview_graph_button_filter($device_id, $ex_addr) that matches a saved Filter on both the Listener and the Stream, including the defined All-filter sentinels:

WHERE (device_id = ? OR device_id = 0)
AND (ex_addr = ? OR ex_addr = ? OR ex_addr = ? OR ex_addr = ?)   -- exporter, '-1', '', '0'
ORDER BY (device_id = ?) DESC, (ex_addr = ?) DESC, id ASC
LIMIT 1

A Filter scoped to a different specific Stream is excluded by the WHERE, so a Listener with Filters for several streams can no longer drill into the wrong one. When more than one Filter matches, the choice is deterministic — an exact Listener first, then an exact Stream, then the lowest id. When nothing matches, the helper returns an empty array and the button links nowhere (return false) rather than falling back to a wrong-stream Filter. New tests cover the two-Filters-on-one-listener scoping and the omit-when-none case.

Blocker 2 — the rematerialize queue was an unsynchronized read-modify-write. The single comma-separated flowview_rematerialize_queue setting is gone. Each Filter is now persisted as its own settings row, flowview_rematerialize_queue_<id>, whose value is a fresh token:

  • Enqueue is a single INSERT ... ON DUPLICATE KEY UPDATE per id, so two concurrent saves write different rows and never overwrite one another.
  • The poller reads each row's token and clears entries with a compare-and-delete (DELETE ... WHERE name = ? AND value = ?), only for the Filters it actually started. A save that re-queues a Filter after the poller's read replaces the token, so the poller's delete no longer matches and the rebuild is kept for the next cycle instead of being lost.

New Pest tests cover the keyed enqueue, the id-ordered listing, the compare-and-delete that survives an overlapping re-queue (the save-and-drain race), the still-running wait, and the deleted-Filter drop. Full suite: 393 passing.

@somethingwithproof somethingwithproof left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two prior blockers are resolved on this head: drilldown selection now scopes listener and stream deterministically, and the queue uses per-filter token rows with compare-and-delete coverage. I cannot provide an independent approval because this PR’s history includes my merge commit f9b5954. Current-head PHP jobs also stop before tests at Composer’s ownership/utime() failure; rebase after #304 or include that workflow fix, rerun green, and obtain an independent approval.

@TheWitness
TheWitness dismissed somethingwithproof’s stale review November 3, 2026 14:47

Issues are resolved now.

TheWitness and others added 2 commits November 3, 2026 09:54
Composer runs as the runner user in a later CI step and calls touch()
while generating the fixture autoloader; utime() only succeeds for the
file owner, so the runner user must own the tree. www-data is retained
as group and ownership is handed back before polling. Mirrors the fix
in Cacti/plugin_evidence#63.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion tokens

The SQL LIKE that loads queued rematerialization settings can match
case-insensitively, so flowview_get_queued_rematerialization_tokens()
has a case-sensitive strncmp guard that skips rows which only match by
case. Add a unit test feeding a case-variant settings row so that guard
(the previously uncovered continue branch) is exercised, restoring 100%
patch coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 3, 2026

Copy link
Copy Markdown

@TheWitness
TheWitness merged commit cedc384 into develop Nov 3, 2026
5 checks passed
@TheWitness
TheWitness deleted the fix/remove-listener-stream branch November 3, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants